package com.lintf.wxpay.service.Impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.google.gson.Gson;
import com.lintf.business.entity.OrderInfo;
import com.lintf.business.entity.RefundInfo;
import com.lintf.common.exception.ServiceException;
import com.lintf.common.utils.string.StringUtils;
import com.lintf.wxpay.base.WxChatPayDto;
import com.lintf.wxpay.base.enums.*;
import com.lintf.wxpay.config.WxPayConfig;
import com.lintf.wxpay.service.OrderInfoService;
import com.lintf.wxpay.service.PaymentInfoService;
import com.lintf.wxpay.service.RefundInfoService;
import com.lintf.wxpay.service.WxPayService;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author lin tingfeng
 * @system Mac
 * @data 2024/11/21 13:57
 * @package_name com.lintf.wxpay.service.Impl
 * @project_name sharding-sphere
 * @desc
 */
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private OrderInfoService orderInfoService;

    @Autowired
    private PaymentInfoService paymentInfoService;

    @Autowired
    private RefundInfoService refundInfoService;

    @Autowired
    private WxPayConfig wxPayConfig;
    @Resource
    private CloseableHttpClient wxPayClient;

    private final ReentrantLock lock = new ReentrantLock();

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> nativePay(WxChatPayDto dto) {
        log.info("NativePay支付下单流程：开始");
        OrderInfo orderInfo = orderInfoService.createOrder(dto, WxPayType.NATIVE);

        try {
            Map<String, Object> nativePay = nativePay(orderInfo);
            log.info("NativePay支付下单流程：完成");
            return nativePay;
        } catch (Exception e) {
            log.error("NativePay支付下单流程：支付下单异常，原因：{}", e.getMessage(), e);
            throw new ServiceException("NativePay支付下单流程：支付下单异常");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> miniProgramPay(WxChatPayDto dto) {
        log.info("MiniProgramPay支付下单流程：开始");
        OrderInfo orderInfo = orderInfoService.createOrder(dto, WxPayType.MINI_PROGRAM);

        try {
            Map<String, Object> nativePay = miniProgramPay(orderInfo, dto.getOpenId());
            log.info("MiniProgramPay支付下单流程：完成");
            return nativePay;
        } catch (Exception e) {
            log.error("MiniProgramPay支付下单流程：支付下单异常，原因：{}", e.getMessage(), e);
            throw new ServiceException("MiniProgramPay支付下单流程：支付下单异常");
        }
    }

    @Override
    public String refunds(String orderNo, String reason) throws IOException {
        log.info("Refunds订单退款流程：开始");
        RefundInfo refundInfo = refundInfoService.createRefundInfoByOrderNo(orderNo, reason);

        // 创建远程调用对象
        String refundUrl = String.format(WxApiType.DOMESTIC_REFUNDS.getUrl(), orderNo);
        refundUrl = wxPayConfig.getDomain().concat(refundUrl);
        HttpPost httpPost = new HttpPost(refundUrl);

        //构造请求参数
        Gson gson = new Gson();
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("out_trade_no", orderNo);
        paramMap.put("out_refund_no", refundInfo.getRefundNo());
        paramMap.put("reason", reason);
        paramMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxPayNotify.REFUND_NOTIFY.getType()));

        // 将支付金额从元转换为分
        BigDecimal refundAmountInCents = getInCents(refundInfo.getRefund());
        BigDecimal totalAmountInCents = getInCents(refundInfo.getTotalFee());

        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("total", totalAmountInCents);
        amountMap.put("refund", refundAmountInCents);
        amountMap.put("currency", "CNY");

        paramMap.put("amount", amountMap);

        String jsonParams = gson.toJson(paramMap);
        log.info("封装请求参数：" + paramMap);

        //封装请求参数
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) { //处理成功
                log.info("Refunds订单退款流程：处理成功，响应码：" + statusCode + ",响应结果：" + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("Refunds订单退款流程：处理成功，响应码：" + statusCode + ",响应结果：" + bodyAsString);
            } else {
                log.error("Refunds订单退款流程：处理失败，响应码：" + statusCode + ",响应结果：" + bodyAsString);
                throw new ServiceException("Refunds订单退款流程：处理失败，响应码：" + statusCode + ",响应结果：" + bodyAsString);
            }

            //TODO 更新订单状态
            log.info("Refunds订单退款流程：更新订单状态");
            updateOrderStatus(orderNo, OrderStatus.REFUND_PROCESSING);

            //TODO 更新退款单
            log.info("Refunds订单退款流程：更新退款单");
            refundInfoService.updateRefund(bodyAsString);

        } finally {
            response.close();
        }
        log.info("Refunds订单退款流程：完成");
        return null;
    }

    @Override
    public void processOrder(Map<String, Object> bodyMap) {
        log.info("ProcessOrder订单处理流程：开始");
        String plainText = decryptFromResource(bodyMap);

        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String orderNo = String.valueOf(plainTextMap.get("out_trade_no"));

        //TODO 并发控制
        //尝试获取锁：成功获取则立即返回true，反之返回false
        if (lock.tryLock()) {
            try {
                //TODO 处理重复通知：保证处理的幂等性
                LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
                wrapper.select(OrderInfo::getOrderStatus).eq(OrderInfo::getOrderNo, orderNo);
                OrderInfo orderInfo = orderInfoService.getOne(wrapper);
                if (orderInfo != null && !StringUtils.equals(OrderStatus.NOTPAY.getType(), orderInfo.getOrderStatus())) {
                    return;
                }

                //TODO 更新订单状态
                log.info("ProcessOrder订单处理流程：更新订单状态");
                updateOrderStatus(orderNo, OrderStatus.SUCCESS);

                //TODO 记录支付日志
                log.info("ProcessOrder订单处理流程：记录支付日志");
                paymentInfoService.createPayment(plainText);
            } finally {
                lock.unlock();
            }
        }

        log.info("ProcessOrder订单处理流程：完成 ");
    }

    @Override
    public void processRefunds(HashMap<String, Object> bodyMap) {
        log.info("ProcessRefunds退款单处理流程：开始");
        String plainText = decryptFromResource(bodyMap);

        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String orderNo = String.valueOf(plainTextMap.get("out_trade_no"));

        //TODO 并发控制
        //尝试获取锁：成功获取则立即返回true，反之返回false
        if (lock.tryLock()) {
            try {
                //TODO 处理重复通知：保证处理的幂等性
                LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
                wrapper.select(OrderInfo::getOrderStatus).eq(OrderInfo::getOrderNo, orderNo);
                OrderInfo orderInfo = orderInfoService.getOne(wrapper);
                if (orderInfo != null && !StringUtils.equals(OrderStatus.REFUND_PROCESSING.getType(), orderInfo.getOrderStatus())) {
                    return;
                }

                //TODO 更新订单状态
                log.info("ProcessRefunds退款单处理流程：更新订单状态");
                updateOrderStatus(orderNo, OrderStatus.REFUND_SUCCESS);

                //TODO 更新退款单
                log.info("ProcessRefunds退款单处理流程：更新退款单");
                refundInfoService.updateRefund(plainText);
            } finally {
                lock.unlock();
            }
        }
        log.info("ProcessRefunds退款单处理流程：完成");
    }

    @Override
    public void cancelOrder(String orderNo) throws Exception {
        log.info("CancelOrder关闭订单流程：开始，订单号：{}", orderNo);

        //TODO 调用微信支付的关闭订单API
        log.info("CancelOrder关闭订单流程：调用微信支付的关闭订单API");
        // 创建远程调用对象
        String closeUrl = String.format(WxApiType.CLOSE_ORDER_BY_NO.getUrl(), orderNo);
        closeUrl = wxPayConfig.getDomain().concat(closeUrl);
        HttpPost httpPost = new HttpPost(closeUrl);

        //构造请求参数
        Gson gson = new Gson();
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("mchid", wxPayConfig.getMchId());
        String jsonParams = gson.toJson(paramMap);
        log.info("封装请求参数：" + paramMap);

        //封装请求参数
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) { //处理成功
                log.info("CancelOrder关闭订单流程：处理成功，响应码：" + statusCode);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("CancelOrder关闭订单流程：处理成功，响应码：" + statusCode);
            } else {
                log.error("CancelOrder关闭订单流程：处理失败，响应码：" + statusCode);
                throw new ServiceException("CancelOrder关闭订单流程：处理失败，响应码：" + statusCode);
            }
        } finally {
            response.close();
        }

        //TODO 更新本地订单状态
        log.info("CancelOrder关闭订单流程：更新本地订单状态");
        updateOrderStatus(orderNo, OrderStatus.CANCEL);

        log.info("CancelOrder关闭订单流程：完成，订单号：{}", orderNo);
    }

    @Override
    public String queryOrder(String orderNo) throws Exception {
        log.info("QueryOrder查询订单流程：开始，订单号：{}", orderNo);
        // 创建远程调用对象
        String queryUrl = String.format(WxApiType.ORDER_QUERY_BY_NO.getUrl(), orderNo);
        queryUrl = wxPayConfig.getDomain().concat(queryUrl).concat("?mchid=").concat(wxPayConfig.getMchId());

        //封装请求参数
        HttpGet httpGet = new HttpGet(queryUrl);
        httpGet.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            String bodyAsString = EntityUtils.toString(response.getEntity());
            if (statusCode == 200) { //处理成功
                log.info("QueryOrder查询订单流程：处理成功，响应码：" + statusCode + "响应结果：" + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("QueryOrder查询订单流程：处理成功，响应码：" + statusCode + "响应结果：" + bodyAsString);
            } else {
                log.error("QueryOrder查询订单流程：处理失败，响应码：" + statusCode + "，响应结果：" + bodyAsString);
                throw new ServiceException("QueryOrder查询订单流程：处理失败，响应码：" + statusCode + "响应结果：" + bodyAsString);
            }

            log.info("QueryOrder查询订单流程：完成");
            log.info("QueryOrder查询订单流程：完成，订单号：{}", orderNo);

            return bodyAsString;
        } finally {
            response.close();
        }
    }

    @Override
    public String queryRefunds(String refundNo) throws IOException {
        log.info("QueryRefunds查询退款单流程：开始，订单号：{}", refundNo);
        // 创建远程调用对象
        String queryUrl = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getUrl(), refundNo);
        queryUrl = wxPayConfig.getDomain().concat(queryUrl);

        //封装请求参数
        HttpGet httpGet = new HttpGet(queryUrl);
        httpGet.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            String bodyAsString = EntityUtils.toString(response.getEntity());
            if (statusCode == 200) { //处理成功
                log.info("NativeQueryOrder查询订单流程：处理成功，响应码：" + statusCode + "响应结果：" + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("NativeQueryOrder查询订单流程：处理成功，响应码：" + statusCode + "响应结果：" + bodyAsString);
            } else {
                log.error("NativeQueryOrder查询订单流程：处理失败，响应码：" + statusCode + "，响应结果：" + bodyAsString);
                throw new ServiceException("NativeQueryOrder查询订单流程：处理失败，响应码：" + statusCode + "响应结果：" + bodyAsString);
            }
            log.info("NativeQueryOrder查询订单流程：完成");
            log.info("QueryRefunds查询退款单流程：完成，订单号：{}", refundNo);
            return bodyAsString;
        } finally {
            response.close();
        }
    }

    @Override
    public void checkOrder(String orderNo) throws Exception {
        log.info("核实订单状态流程：开始，订单号：{}", orderNo);
        String queryOrder = queryOrder(orderNo);

        Gson gson = new Gson();
        HashMap resultMap = gson.fromJson(queryOrder, HashMap.class);

        String tradeState = String.valueOf(resultMap.get("trade_state"));
        log.info("核实订单状态流程：核实，订单号：{}，状态：{}", orderNo, tradeState);
        if (StringUtils.equals(WxTradeStatus.SUCCESS.getType(), tradeState)) {
            //TODO 更新订单状态
            updateOrderStatus(orderNo, OrderStatus.SUCCESS);

            //TODO 记录支付日志
            paymentInfoService.createPayment(queryOrder);
        }
        if (StringUtils.equals(WxTradeStatus.NOTPAY.getType(), tradeState)) {

            //TODO 调用微信关闭订单API
            cancelOrder(orderNo);

        }
        log.info("核实订单状态流程：完成，订单号：{}", orderNo);
    }

    private void updateOrderStatus(String orderNo, OrderStatus orderStatus) {
        LambdaUpdateWrapper<OrderInfo> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(OrderInfo::getOrderNo, orderNo)
                .set(OrderInfo::getOrderStatus, orderStatus.getType())
                .set(OrderInfo::getUpdateTime, new Date());
        orderInfoService.update(updateWrapper);
    }

    private String decryptFromResource(Map<String, Object> bodyMap) {
        log.info("密文解密流程：开始");
        // 通知数据
        Map<String, String> resourceMap = (Map<String, String>) bodyMap.get("resource");
        // 数据密文
        String ciphertext = resourceMap.get("ciphertext");
        // 随即串
        String nonce = resourceMap.get("nonce");
        // 附加数据
        String associatedData = resourceMap.get("associated_data");
        // 解密
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String plainText;
        try {
            log.info("密文解密流程：等待密文解密，密文：{}", ciphertext);
            plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
            log.info("密文解密流程：已完成密文解密，明文：{}", plainText);
        } catch (GeneralSecurityException e) {
            log.info("密文解密流程：密文解密发生异常：{}", e.getMessage(), e);
            throw new ServiceException("密文解密流程：密文解密发生异常：" + e.getMessage());
        }
        log.info("密文解密流程：完成");
        return plainText;
    }

    private Map<String, Object> nativePay(OrderInfo orderInfo) throws Exception {
        Map<String, Object> result = new HashMap<>();
        log.info("正在查询是否存在未过期付款二维码URL...");
        if (StringUtils.isNotEmpty(orderInfo.getCodeUrl()) && isWithinTwoHours(orderInfo.getUpdateTime())) {
            // 构造返回参数
            log.info("存在未过期付款二维码URL");
            result.put("codeUrl", orderInfo.getCodeUrl());
            result.put("orderNo", orderInfo.getOrderNo());
            return result;
        }
        log.info("NativePay获取付款二维码流程：开始");
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getUrl()));
        // 请求body参数
        Gson gson = new Gson();
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("appid", wxPayConfig.getAppId());
        paramMap.put("mchid", wxPayConfig.getMchId());
        paramMap.put("description", orderInfo.getTitle());
        paramMap.put("out_trade_no", orderInfo.getOrderNo());
        paramMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxPayNotify.PAY_NOTIFY.getType()));

        BigDecimal totalAmountInCents = getInCents(orderInfo.getTotalFee());

        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("total", totalAmountInCents);
        amountMap.put("currency", "CNY");
        paramMap.put("amount", amountMap);

        String jsonParams = gson.toJson(paramMap);
        log.info("封装请求参数：" + paramMap);

        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            String bodyAsString = EntityUtils.toString(response.getEntity());
            if (statusCode == 200) { //处理成功
                log.info("NativePay获取付款二维码流程：处理成功，响应体：" + statusCode + "，响应体：" + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("NativePay获取付款二维码流程：处理成功，响应体：" + statusCode + "，响应体：" + bodyAsString);
            } else {
                log.error("NativePay获取付款二维码流程：处理失败，响应码：" + statusCode + "，响应体：" + bodyAsString);
                throw new ServiceException("NativePay获取付款二维码流程：处理失败，响应码：" + statusCode + "，响应体：" + bodyAsString);
            }
            HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);

            // 获取支付二维码URL
            String codeUrl = resultMap.get("code_url");

            // 保存支付二维码URL
            saveCodeUrl(orderInfo.getOrderNo(), codeUrl);

            // 构造返回参数
            result.put("codeUrl", codeUrl);
            result.put("orderNo", orderInfo.getOrderNo());
            log.info("NativePay获取付款二维码流程：完成");
            return result;
        } finally {
            response.close();
        }
    }

    @NotNull
    private static BigDecimal getInCents(BigDecimal bigDecimal) {
        // 将支付金额从元转换为分
        return bigDecimal.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP);
    }

    private Map<String, Object> miniProgramPay(OrderInfo orderInfo, String openId) throws Exception {
        Map<String, Object> result = new HashMap<>();
        log.info("正在查询是否存在未过期预支付交易会话标识...");
        if (StringUtils.isNotEmpty(orderInfo.getPrepayId()) && isWithinTwoHours(orderInfo.getUpdateTime())) {
            // 构造返回参数
            log.info("存在未过期预支付交易会话标识");
            // 构造返回参数
            return signaturePackage(orderInfo.getPrepayId(), wxPayConfig.getAppId());
        }
        log.info("MiniProgramPay预支付流程：开始");
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.MINI_PROGRAM_PAY.getUrl()));
        // 请求body参数
        Gson gson = new Gson();
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("appid", wxPayConfig.getAppId());
        paramMap.put("mchid", wxPayConfig.getMchId());
        paramMap.put("description", orderInfo.getTitle());
        paramMap.put("out_trade_no", orderInfo.getOrderNo());
        paramMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxPayNotify.PAY_NOTIFY.getType()));

        // 将支付金额从元转换为分
        BigDecimal totalAmountInCents = getInCents(orderInfo.getTotalFee());

        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("total", totalAmountInCents);
        amountMap.put("currency", "CNY");
        paramMap.put("amount", amountMap);

        Map<String, Object> payerMap = new HashMap<>();
        payerMap.put("openid", openId);
        paramMap.put("payer", payerMap);

        String jsonParams = gson.toJson(paramMap);
        log.info("封装请求参数：" + paramMap);

        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            String bodyAsString = EntityUtils.toString(response.getEntity());
            if (statusCode == 200) { //处理成功
                log.info("MiniProgramPay预支付流程：处理成功，响应体：" + statusCode + "，响应体：" + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("MiniProgramPay预支付流程：处理成功，响应体：" + statusCode + "，响应体：" + bodyAsString);
            } else {
                log.error("MiniProgramPay预支付流程：处理失败，响应码：" + statusCode + "，响应体：" + bodyAsString);
                throw new ServiceException("MiniProgramPay预支付流程：处理失败，响应码：" + statusCode + "，响应体：" + bodyAsString);
            }
            HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);

            // 获取预支付交易会话标识。用于后续接口调用中使用，该值有效期为2小时
            String prepayId = resultMap.get("prepay_id");

            // 保存预支付交易会话标识
            savePrepayId(orderInfo.getOrderNo(), prepayId);

            // 构造返回参数
            Map<String, Object> stringObjectMap = signaturePackage(prepayId, wxPayConfig.getAppId());
            log.info("MiniProgramPay预支付流程：完成");
            return stringObjectMap;
        } finally {
            response.close();
        }
    }

    /**
     * 支付请求
     *
     * @param prepayId 微信支付返回的prepayId响应数据
     * @return 签名时数据和签名一起返回前端用于前端吊起支付
     */
    public Map<String, Object> signaturePackage(String prepayId, String appId) throws Exception {
        // 得到当前系统时间搓
        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
        // 获取随机字符
        String nonceStr = IdUtil.fastSimpleUUID();
        // 获取签名
        String paySign;
        try {
            StringBuilder sb = new StringBuilder();
            // 应用id
            sb.append(appId).append("\n");
            // 支付签名时间戳
            sb.append(timeStamp).append("\n");
            // 随机字符串
            sb.append(nonceStr).append("\n");
            // 预支付交易会话ID  这个要注意 key = "prepay_id=xxxxxx"
            sb.append("prepay_id=").append(prepayId).append("\n");
            // 签名
            Signature sign = Signature.getInstance("SHA256withRSA");
            // 获取商户私钥并进行签名
            PrivateKey privateKey = wxPayConfig.getPrivateKey(wxPayConfig.getPrivateKeyPath());
            sign.initSign(privateKey);
            sign.update(sb.toString().getBytes(StandardCharsets.UTF_8));
            // 得到签名
            paySign = Base64.getEncoder().encodeToString(sign.sign());
        } catch (Exception e) {
            log.error("MiniProgramPay预支付流程：生成交易签名失败", e);
            throw new ServiceException("MiniProgramPay预支付流程：生成交易签名失败");
        }

        // 将签名时数据和签名一起返回前端用于前端吊起支付
        Map<String, Object> map = new HashMap<>();
        // 小程序id
        map.put("appId", appId);
        // 时间戳
        map.put("timeStamp", timeStamp);
        // 随机字符串
        map.put("nonceStr", nonceStr);
        // 预支付交易会话ID
        map.put("package", "prepay_id=" + prepayId);
        // 签名方式
        map.put("signType", "RSA");
        // 签名
        map.put("paySign", paySign);
        return map;
    }

    private void saveCodeUrl(String orderNo, String codeUrl) {
        LambdaUpdateWrapper<OrderInfo> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(OrderInfo::getOrderNo, orderNo)
                .set(OrderInfo::getCodeUrl, codeUrl)
                .set(OrderInfo::getUpdateTime, new Date());
        orderInfoService.update(wrapper);
    }

    private void savePrepayId(String orderNo, String prepayId) {
        LambdaUpdateWrapper<OrderInfo> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(OrderInfo::getOrderNo, orderNo)
                .set(OrderInfo::getPrepayId, prepayId)
                .set(OrderInfo::getUpdateTime, new Date());
        orderInfoService.update(wrapper);
    }

    public static boolean isWithinTwoHours(Date date) {
        double nh = 1000 * 60 * 60 * 1.00;
        // 将 Date 转换为 LocalDateTime
        long dateTime = date.getTime();
        long nowTime = new Date().getTime();
        long between = nowTime - dateTime;
        // 计算差多少小时
        double hour = between / nh;
        // 检查持续时间是否小于等于2小时
        return hour <= 2.00;
    }
}

